/*
 * MIT License
 *
 * Copyright (c) 2019 Mr.css
 *
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 * of this software and associated documentation files (the "Software"), to deal
 * in the Software without restriction, including without limitation the rights
 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 * copies of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in all
 * copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
 * SOFTWARE.
 *
 */

package cn.seaboot.commons.file;

import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.Reader;
import java.nio.charset.Charset;

/**
 * 按字节/字符串读取读取
 *
 * @author by Mr.css on 2018/6/8.
 */
public class ReaderUtils {
    private ReaderUtils() {
    }

    private static final int EOF = -1;

    /**
     * Get the contents of an <code>InputStream</code> as a <code>byte[]</code>.
     * Use this method instead of <code>toByteArray(InputStream)</code>
     * when <code>InputStream</code> size is known
     *
     * @param input the <code>InputStream</code> to read from
     * @param size  the size of <code>InputStream</code>
     * @return the requested byte array
     * @throws IOException              if an I/O error occurs or <code>InputStream</code> size differ from parameter size
     * @throws IllegalArgumentException if size is less than zero
     */
    public static byte[] readAsByteArray(InputStream input, int size) throws IOException {
        if (size < 0) {
            throw new IllegalArgumentException("Size must be equal or greater than zero: " + size);
        }
        if (size == 0) {
            return new byte[0];
        }
        byte[] data = new byte[size];
        int offset = 0;
        int readed;
        while (offset < size && (readed = input.read(data, offset, size - offset)) != -1) {
            offset += readed;
        }
        if (offset != size) {
            throw new IOException("Unexpected readed size. current: " + offset + ", excepted: " + size);
        }
        return data;
    }

    /**
     * Get the contents of a <code>Reader</code> as a <code>byte[]</code>
     * using the default character encoding of the platform.
     * <p>
     * This method buffers the input internally, so there is no need to use a
     * <code>BufferedReader</code>.
     *
     * @param input the <code>Reader</code> to read from
     * @return the requested byte array
     * @throws NullPointerException if the input is null
     * @throws IOException          if an I/O error occurs
     */
    public static byte[] readAsByteArray(Reader input) throws IOException {
        ByteArrayOutputStream output = new ByteArrayOutputStream();
        IOUtils.copy(input, output);
        return output.toByteArray();
    }

    /**
     * Get the contents of a <code>InputStream</code> as a <code>byte[]</code>
     * using the default character encoding of the platform.
     * <p>
     *
     * @param input the <code>Reader</code> to read from
     * @return the requested byte array
     * @throws NullPointerException if the input is null
     * @throws IOException          if an I/O error occurs
     */
    public static byte[] readAsByteArray(InputStream input) throws IOException {
        ByteArrayOutputStream output = new ByteArrayOutputStream();
        IOUtils.copy(input, output);
        return output.toByteArray();
    }

    /**
     * 读取成字符串
     *
     * @param input 输入
     * @return 字符串
     * @throws IOException -
     */
    public static String readAsString(InputStream input) throws IOException {
        return readAsString(input, Charset.defaultCharset());
    }


    /**
     * 读取成字符串
     *
     * @param input   输入
     * @param charset 编码
     * @return 字符串
     * @throws IOException -
     */
    public static String readAsString(InputStream input, String charset) throws IOException {
        return readAsString(input, Charset.forName(charset));
    }

    /**
     * 读取成字符串
     *
     * @param input   输入
     * @param charset 编码
     * @return 字符串
     * @throws IOException -
     */
    public static String readAsString(InputStream input, Charset charset) throws IOException {
        StringBuilder sb = new StringBuilder();
        byte[] buffer = new byte[IOUtils.DEFAULT_BUFFER_SIZE];
        int len;
        while ((len = read(input, buffer)) > 0) {
            sb.append(new String(buffer, 0, len, charset));
        }
        return sb.toString();
    }


    /**
     * 从输入流中读取字节。
     * 此实现保证它将读取尽可能多的字节
     * 在放弃之前尽可能； 情况并非总是如此
     * {@link InputStream}的子类。
     *
     * @param input  从哪里读取输入
     * @param buffer 冲区目标
     * @return 实际读取的长度； 如果达到EOF，则可能会少于要求
     * @throws IOException 如果发生读取错误，则抛出IOException
     **/
    public static int read(InputStream input, byte[] buffer) throws IOException {
        return read(input, buffer, 0, buffer.length);
    }


    /**
     * 从输入流中读取字节。
     * 此实现保证它将读取尽可能多的字节
     * 在放弃之前尽可能； 情况并非总是如此
     * {@link InputStream}的子类。
     *
     * @param input  从哪里读取输入
     * @param buffer 冲区目标
     * @param offset 初始偏移量到缓冲区
     * @param length 要读取的长度，必须> = 0
     * @return 实际读取的长度； 如果达到EOF，则可能会少于要求
     * @throws IOException 如果发生读取错误，则抛出IOException
     **/
    public static int read(InputStream input, byte[] buffer, int offset, int length) throws IOException {
        if (length < 0) {
            throw new IllegalArgumentException("Length must not be negative: " + length);
        }
        int remaining = length;
        while (remaining > 0) {
            int location = length - remaining;
            int count = input.read(buffer, offset + location, remaining);
            if (EOF == count) { // EOF
                break;
            }
            remaining -= count;
        }
        return length - remaining;
    }


    /**
     * 从输入流中读取字节。
     * 此实现保证它将读取尽可能多的字节
     * 在放弃之前尽可能； 情况并非总是如此
     * {@link InputStream}的子类。
     *
     * @param input  从哪里读取输入
     * @param buffer 冲区目标
     * @return 实际读取的长度； 如果达到EOF，则可能会少于要求
     * @throws IOException 如果发生读取错误，则抛出IOException
     **/
    public static int read(Reader input, char[] buffer) throws IOException {
        return read(input, buffer, 0, buffer.length);
    }


    /**
     * 从输入流中读取字节。
     * 此实现保证它将读取尽可能多的字节
     * 在放弃之前尽可能； 情况并非总是如此
     * {@link Reader}的子类。
     *
     * @param input  从哪里读取输入
     * @param buffer 冲区目标
     * @param offset 初始偏移量到缓冲区
     * @param length 要读取的长度，必须> = 0
     * @return 实际读取的长度； 如果达到EOF，则可能会少于要求
     * @throws IOException 如果发生读取错误，则抛出IOException
     **/
    public static int read(Reader input, char[] buffer, int offset, int length) throws IOException {
        if (length < 0) {
            throw new IllegalArgumentException("Length must not be negative: " + length);
        }
        int remaining = length;
        while (remaining > 0) {
            int location = length - remaining;
            int count = input.read(buffer, offset + location, remaining);
            if (EOF == count) {
                break;
            }
            remaining -= count;
        }
        return length - remaining;
    }
}
